﻿//Copyright 2011 Kael Russell
//This file is part of il2dynamiccampaign.
//
//il2dynamiccampaign is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//il2dynamiccampaign is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with il2dynamiccampaign.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.IO;
using maddox.game;

//using System.Diagnostics;
using System.Text;
using System.Linq;
using System.Windows.Forms;

namespace TEF.dynCampaign
{
    public partial class DynamicMission
    {
        #region variables

        private List<MissionType> playerMissions = new List<MissionType>();
        private List<MissionType> aiMissions = new List<MissionType>();
        private List<GroundTarget> redGroundTargets = new List<GroundTarget>();
        private List<GroundTarget> blueGroundTargets = new List<GroundTarget>();

        #endregion

        private void createNextMission()
        {
            ISectionFile nextMission = GamePlay.gpLoadSectionFile(PATH + "Files/base.mis");
            backupLastMission();

            removeAllFlights(nextMission);
            createPlayerFlight(nextMission);
            randomizeWeather(nextMission);
            updateSettings();
            buildWeightedObjectLists();
            agePlayerFlight(nextMission, 4);

            int numAircraft;
            MissionInfo missInfo = new MissionInfo();
            Random rand = new Random();

            numAircraft = rand.Next(minAircraft, maxAircraft);

            #region playerMission

            MissionType playerMissionType = WeightedRandomization.Choose(playerMissions.Cast<IWeightedObject>().ToList<IWeightedObject>(), rand) as MissionType;

            setTakeoffWaypoint(nextMission, PLAYER_FLIGHT, PLAYER_AIRBASE_X, PLAYER_AIRBASE_Y);

            missInfo.missionType = playerMissionType.type;

            switch (playerMissionType.type)
            {
                case "BomberEscort":
                    playerBomberEscort(nextMission, missInfo, rand, ref numAircraft);
                    break;
                case "FreeHunt":
                    playerFreeHunt(nextMission, missInfo, rand, ref numAircraft);
                    break;
                case "BomberIntercept":
                    playerBomberIntercept(nextMission, missInfo, rand, ref numAircraft);
                    break;
                case "JaboEscort":
                    playerJaboEscort(nextMission, missInfo, rand, ref numAircraft);
                    break;
                case "Jabo":
                    playerJabo(nextMission, missInfo, rand, ref numAircraft);
                    break;
                case "LevelBomb":
                    playerLevelBomb(nextMission, missInfo, rand, ref numAircraft);
                    break;
                default:
                    playerFreeHunt(nextMission, missInfo, rand, ref numAircraft);
                    break;
            }

            setLandingWaypoint(nextMission, PLAYER_FLIGHT, PLAYER_AIRBASE_X, PLAYER_AIRBASE_Y, missInfo.altitude);

            #endregion

            #region aiMission

            MissionType AiMissionType;

            do
            {
                int aircraft;
                string bomberFlight;

                AiMissionType = WeightedRandomization.Choose(aiMissions.Cast<IWeightedObject>().ToList<IWeightedObject>(), rand) as MissionType;

                if (!validMission(AiMissionType))
                    continue;

                switch (AiMissionType.type)
                {
                    case "RedPatrol":
                        aircraft = rand.Next(3, 7);
                        createRandomRedFighterPatrol(nextMission, aircraft);
                        numAircraft -= aircraft;
                        break;
                    case "BluePatrol":
                        aircraft = rand.Next(2, 9);
                        createRandomBlueFighterPatrol(nextMission, aircraft);
                        numAircraft -= aircraft;
                        break;
                    case "RedBombers":
                        aircraft = rand.Next(2, 9);

                        numAircraft -= aircraft;
                        bomberFlight = createRandomBomberFlight(nextMission, 1, aircraft);
                        if (rand.Next(0, 2) == 1 && numAircraft > 5 && aircraft > 3)
                        {
                            aircraft = rand.Next(2, 7);
                            createRedBomberEscort(nextMission, bomberFlight, aircraft);
                            numAircraft -= aircraft;
                        }
                        break;
                    case "BlueBombers":
                        aircraft = rand.Next(2, 9);

                        numAircraft -= aircraft;
                        bomberFlight = createRandomBomberFlight(nextMission, 2, aircraft);
                        if (rand.Next(0, 2) == 1 && numAircraft > 5 && aircraft > 3)
                        {
                            aircraft = rand.Next(2, 5);
                            createBlueBomberEscort(nextMission, bomberFlight, aircraft);
                            numAircraft -= aircraft;
                        }
                        break;
                    case "BlueJabo":
                        aircraft = rand.Next(2, 7);

                        numAircraft -= aircraft;
                        bomberFlight = createJaboFlight(nextMission, 2, aircraft);
                        if (rand.Next(0, 2) == 1 && numAircraft > 5 && aircraft > 3)
                        {
                            aircraft = rand.Next(2, 5);
                            createBlueBomberEscort(nextMission, bomberFlight, aircraft);
                            numAircraft -= aircraft;
                        }
                        break;
                }
            } while (numAircraft > 0);

            #endregion

            addStatic(nextMission, "Files/ground.mis");
            nextMission.save(PATH + "current.mis");

            createBriefings(PATH + "current.BRIEFING", missInfo);
        }

        #region playerMissions

        private void playerLevelBomb(ISectionFile nextMission, MissionInfo missInfo, Random rand, ref int numAircraft)
        {
            int playerArmy, opposingArmy;
            getArmies(out playerArmy, out opposingArmy);

            chooseRandomGroundTarget(missInfo, rand, opposingArmy);

            setSmallAiPatrol(nextMission, PLAYER_FLIGHT, PLAYER_AIRBASE_X, PLAYER_AIRBASE_Y, missInfo.altitude, 1);

            setLevelBombWaypoint(nextMission, PLAYER_FLIGHT, missInfo.xPos, missInfo.yPos, missInfo.altitude);

            int escort = rand.Next(4, 9);
            numAircraft -= escort;
            createBomberEscort(nextMission, playerArmy, PLAYER_FLIGHT, escort);

            int fighters = rand.Next(4, 9);
            numAircraft -= fighters;
            createAiInterceptFlight(nextMission, opposingArmy, fighters, missInfo.xPos, missInfo.yPos, missInfo.altitude);
        }

        private void playerJabo(ISectionFile nextMission, MissionInfo missInfo, Random rand, ref int numAircraft)
        {
            int playerArmy, opposingArmy;
            getArmies(out playerArmy, out opposingArmy);

            missInfo.altitude = 1000;

            setSmallAiPatrol(nextMission, PLAYER_FLIGHT, PLAYER_AIRBASE_X, PLAYER_AIRBASE_Y, missInfo.altitude, 1);

            setJaboWaypoint(nextMission, PLAYER_FLIGHT, playerArmy, missInfo, "Aircraft");

            int escort = rand.Next(2, 7);
            numAircraft -= escort;
            createBomberEscort(nextMission, playerArmy, PLAYER_FLIGHT, escort);

            int fighters = rand.Next(2, 9);
            numAircraft -= fighters;
            createAiInterceptFlight(nextMission, opposingArmy, fighters, missInfo.xPos, missInfo.yPos, missInfo.altitude);

            setPlayerJaboWeapons(nextMission);
        }

        private void playerJaboEscort(ISectionFile nextMission, MissionInfo missInfo, Random rand, ref int numAircraft)
        {
            int playerArmy, opposingArmy;
            getArmies(out playerArmy, out opposingArmy);

            int jabos = rand.Next(2, 11);
            numAircraft -= jabos;

            string jaboFlight = createJaboFlight(nextMission, playerArmy, jabos, missInfo, true);

            setPlayerEscort(nextMission, jaboFlight);

            int fighters = rand.Next(2, 4);
            numAircraft -= fighters;

            createAiInterceptFlight(nextMission, opposingArmy, fighters, missInfo.xPos, missInfo.yPos, missInfo.altitude);
        }

        private void playerBomberIntercept(ISectionFile nextMission, MissionInfo missInfo, Random rand, ref int numAircraft)
        {
            int playerArmy, opposingArmy;
            getArmies(out playerArmy, out opposingArmy);

            int maxBombers = numAircraft / 4;
            if (maxBombers < 2)
                maxBombers = 2;

            int bombers = rand.Next(2, maxBombers);
            int fighters = rand.Next(2, maxBombers);
            numAircraft -= bombers;
            numAircraft -= fighters;

            string bomberFlight = createRandomBomberFlight(nextMission, opposingArmy, bombers, missInfo, false);
            createBomberEscort(nextMission, opposingArmy, bomberFlight, fighters);

            setAiPatrol(nextMission, PLAYER_FLIGHT, missInfo.xPos, missInfo.yPos, missInfo.altitude, 4);

            if (fighters >= 8 && numAircraft > 4)
            {
                int help = rand.Next(2, 5);
                numAircraft -= help;
                createAiInterceptFlight(nextMission, playerArmy, help, missInfo.xPos, missInfo.yPos, missInfo.altitude);
            }
        }

        private void playerFreeHunt(ISectionFile nextMission, MissionInfo missInfo, Random rand, ref int numAircraft)
        {
            int playerArmy, opposingArmy;
            getArmies(out playerArmy, out opposingArmy);

            setCoastalFreeHuntWaypoint(nextMission, PLAYER_FLIGHT, missInfo);
            int fighters = rand.Next(2, 7);

            createAiInterceptFlight(nextMission, opposingArmy, fighters, missInfo.xPos, missInfo.yPos, missInfo.altitude);
            numAircraft -= fighters;
        }

        private void playerBomberEscort(ISectionFile nextMission, MissionInfo missInfo, Random rand, ref int numAircraft)
        {
            int playerArmy, opposingArmy;
            getArmies(out playerArmy, out opposingArmy);

            int bombers = rand.Next(3, (numAircraft / 3));
            numAircraft -= bombers;

            int fighters = rand.Next(2, 7);

            string bomberFlight = createRandomBomberFlight(nextMission, playerArmy, bombers, missInfo, true);
            setPlayerEscort(nextMission, bomberFlight);

            createAiInterceptFlight(nextMission, opposingArmy, fighters, missInfo.xPos, missInfo.yPos, missInfo.altitude);
            numAircraft -= fighters;

            if (numAircraft > 12 && bombers > 8)
            {
                fighters = rand.Next(3, 7);
                createAiInterceptFlight(nextMission, opposingArmy, fighters, missInfo.xPos, missInfo.yPos, missInfo.altitude);
                numAircraft -= fighters;

                fighters = rand.Next(2, 9);
                createBomberEscort(nextMission, playerArmy, bomberFlight, fighters);
            }
        }

        #endregion

        #region briefings

        /// <summary>
        /// Creates appropriate briefings for the mission and saves them.
        /// </summary>
        /// <param name="path">The file to save the briefings to.</param>
        /// <param name="mission">The MissionInfo object that the briefings will describe.</param>
        private void createBriefings(string path, MissionInfo mission)
        {
            path = path.Remove(0, 6);
            string[] baseBriefing = File.ReadAllLines(PATH.Remove(0, 6) + "Files/base.BRIEFING");
            StringBuilder newFile = new StringBuilder();

            string name = "";
            for (int i = 0; i < baseBriefing.Length; i++)
            {
                newFile.Append(baseBriefing[i] + "\r\n");

                if (baseBriefing[i].Equals("<Name>"))
                    name = baseBriefing[i + 1];

                if (baseBriefing[i].Equals("<Description>") && name.Equals("Intro"))
                {
                    addMissionIntro(newFile, mission);
                    addPlayerStats(newFile);
                }
            }

            File.WriteAllText(path, newFile.ToString());
        }

        /// <summary>
        /// Adds appropriate text to the string builder based on the MissionInfo object.
        /// </summary>
        /// <param name="sb">The string builder to add the text to.</param>
        /// <param name="mission">The MissionInfo object that determines the briefing.</param>
        private void addMissionIntro(StringBuilder sb, MissionInfo mission)
        {
            if (mission.sector.Equals(""))
                mission.sector = GamePlay.gpSectorName(mission.xPos, mission.yPos);

            sb.Append(player.getDate().ToString("D") + "\r\n\r\n");

            switch(mission.missionType)
            {
                case "BomberEscort":
                    {
                        sb.Append("Today you will be escorting a flight of " + mission.targetType + "s. They will be bombing targets in sector " + mission.sector + " at an altitude of " + mission.altitude + " meters. The bombers are " +
                            "already in the air. You are to meet them over our airbase where they will circle once  before heading out.\r\n");
                        break;
                    }

                case "FreeHunt":
                    {
                        sb.Append("Today you will patrol sector " + mission.sector + " at an altitude of " + mission.altitude + " meters. Shoot down any enemy aircraft you see.\r\n");
                        break;
                    }
                case "BomberIntercept":
                    {
                        sb.Append("Intelligence reports a strong chance of bombers attacking " + mission.sector + " at an altitude of " + mission.altitude + " meters. " +
                            "Try to intercept them before they reach their target.");
                        break;
                    }
                case "JaboEscort":
                    {
                        sb.Append("Today you will be escorting a flight of " + mission.targetType + "s. They will be attacking targets in sector " + mission.sector + " at an altitude of " + mission.altitude + " meters. " +
                            "You are to meet them over our airbase where they will circle once before heading out.\r\n");
                        break;
                    }
                case "LevelBomb":
                case "Jabo":
                    {
                        sb.Append("Today you will be attacking " + mission.targetType + " in sector " + mission.sector + " at an altitude of " + mission.altitude + " meters. Your escort is waiting for you.");
                        break;
                    }
            }
        }

        /// <summary>
        /// Adds the players stats to the string builder.
        /// </summary>
        /// <param name="sb">The string builder to add the text to.</param>
        private void addPlayerStats(StringBuilder sb)
        {
            sb.Append("\r\n\r\n");

            sb.Append("Sorties Flown: " + player.getSorties() + "\r\n");
            sb.Append("Victories: " + player.getVictories() + "\r\n");

            sb.Append(player.getListofVictories());
        }

        #endregion

        #region misc

        private bool validMission(MissionType mission)
        {
            bool valid = true;

            switch (mission.type)
            {
                case "RedPatrol":
                    if (!existsAircraftType("Fighters", 1))
                        valid = false;
                    break;
                case "BluePatrol":
                    if (!existsAircraftType("Fighters", 2))
                        valid = false;
                    break;
                case "RedBombers":
                    if (!existsAircraftType("Bombers", 1))
                        valid = false;
                    break;
                case "BlueBombers":
                    if (!existsAircraftType("Bombers", 2))
                        valid = false;
                    break;
                case "BlueJabo":
                    if (!existsAircraftType("Jabo", 2))
                        valid = false;
                    break;
            }

            return valid;
        }

        private bool existsAircraftType(string type, int army)
        {
            ISectionFile aircraftFile;
            if (army == 1)
                aircraftFile = redAircraft;
            else
                aircraftFile = blueAircraft;

            for (int i = 0; i < aircraftFile.lines(type); i++)
            {
                string group, value, aircraftClass;

                aircraftFile.get(type, i, out group, out value);

                aircraftClass = aircraftFile.get(group, "Class");

                if (campaignInfo.get("Aircraft", "Included").Contains(aircraftClass))
                    return true;

                if (aircraftFile.exist(group, "JaboClass"))
                {
                    aircraftClass = aircraftFile.get(group, "JaboClass");

                    if (campaignInfo.get("Aircraft", "Included").Contains(aircraftClass))
                        return true;
                }
            }
            
            return false;
        }

        private void chooseRandomGroundTarget(MissionInfo info, Random rand, int opposingArmy)
        {
            GroundTarget target;

            if (opposingArmy == 1)
                target = WeightedRandomization.Choose(blueGroundTargets.Cast<IWeightedObject>().ToList<IWeightedObject>(), rand) as GroundTarget;
            else
                target = WeightedRandomization.Choose(redGroundTargets.Cast<IWeightedObject>().ToList<IWeightedObject>(), rand) as GroundTarget;

            info.targetType = target.name;
            info.altitude = rand.Next(2000, 5001);

            if (target.ID == "randomAirfield")
            {
                getRandomAirbase(info, opposingArmy);
                return;
            }

            info.xPos = target.xPos;
            info.yPos = target.yPos;
            info.sector = GamePlay.gpSectorName(info.xPos, info.yPos);
        }

        private void setPlayerJaboWeapons(ISectionFile file)
        {
            ISectionFile playerAircraft = GamePlay.gpLoadSectionFile(PATH + "Files/playerAircraft.SECTION");

            string playerSection = PLAYER_FLIGHT.Split('.')[0];

            file.set(PLAYER_FLIGHT, "Weapons", playerAircraft.get(playerSection, "JaboWeapons"));
        }

        private void backupLastMission()
        {
            ISectionFile thisMission = GamePlay.gpLoadSectionFile(PATH + "current.mis");
            ISectionFile thisBriefing = GamePlay.gpLoadSectionFile(PATH + "current.BRIEFING");

            thisMission.save(PATH + "Files/last.mis");
            thisBriefing.save(PATH + "Files/last.BRIEFING");
        }

        private void getArmies(out int playerArmy, out int opposingArmy)
        {
            playerArmy = Int32.Parse(campaignInfo.get("Info", "Army"));

            if (playerArmy == 1)
                opposingArmy = 2;
            else
                opposingArmy = 1;
        }

        private void buildWeightedObjectLists()
        {
            ISectionFile targets = GamePlay.gpLoadSectionFile(PATH + "/Files/groundTargets.SECTION");
            ISectionFile missions = GamePlay.gpLoadSectionFile(PATH + "/Files/missionTypes.SECTION");
            
            string playerMissionArmy;
            if (campaignInfo.get("Info", "Army") == "1")
                playerMissionArmy = "UK" + campaignInfo.get("Info", "Type");
            else
                playerMissionArmy = "DE" + campaignInfo.get("Info", "Type");

            for (int i = 0; i < missions.lines(playerMissionArmy); i++)
            {
                string type, weight;
                missions.get(playerMissionArmy, i, out type, out weight);
                playerMissions.Add(new MissionType(type, Int32.Parse(weight)));
            }

            for (int i = 0; i < missions.lines("AiMissions"); i++)
            {
                string type, weight;
                missions.get("AiMissions", i, out type, out weight);
                aiMissions.Add(new MissionType(type, Int32.Parse(weight)));
            }

            for (int i = 0; i < targets.lines("UKTargets"); i++)
            {
                string ID, value;
                targets.get("UKTargets", i, out ID, out value);

                string[] values = value.Split(' ');
                int weight = Int32.Parse(values[0]);
                int xPos = Int32.Parse(values[1]);
                int yPos = Int32.Parse(values[2]);

                string name = value.Substring(value.LastIndexOf("<name>"));
                name = name.Replace("<name>", "");
                name = name.Trim();

                redGroundTargets.Add(new GroundTarget(ID, name, weight, xPos, yPos));
            }

            for (int i = 0; i < targets.lines("DETargets"); i++)
            {
                string ID, value;
                targets.get("DETargets", i, out ID, out value);

                string[] values = value.Split(' ');
                int weight = Int32.Parse(values[0]);
                int xPos = Int32.Parse(values[1]);
                int yPos = Int32.Parse(values[2]);

                string name = value.Substring(value.LastIndexOf("<name>"));
                name = name.Replace("<name>", "");
                name = name.Trim();

                blueGroundTargets.Add(new GroundTarget(ID, name, weight, xPos, yPos));
            }
        }

        #endregion
    }

    #region classes

    /// <summary>
    /// Contains all information relevant to a mission such as coordinates, altitude, etc.
    /// </summary>
    internal class MissionInfo
    {
        public string targetType { get; set; }
        public string sector { get; set; }
        public string missionType { get; set; }

        public int xPos { get; set; }
        public int yPos { get; set; }
        public int altitude { get; set; }
        public int numAircraft { get; set; }

        public MissionInfo()
        {
            xPos = yPos = altitude = numAircraft = 0;
            targetType = sector = missionType = "";
        }
    }

    /// <summary>
    /// Represents a type of mission.
    /// </summary>
    internal class MissionType : IWeightedObject
    {
        public string type { get; private set; }
        public int weight { get; set; }

        public MissionType(string type, int weight)
        {
            this.type = type;
            this.weight = weight;
        }
    }

    /// <summary>
    /// Represents a ground target.
    /// </summary>
    internal class GroundTarget : IWeightedObject
    {
        public string ID { get; private set; }
        public string name { get; private set; }
        public int xPos { get; private set; }
        public int yPos { get; private set; }
        public int weight { get; set; }

        public GroundTarget(string ID, string name, int weight, int xPos, int yPos)
        {
            this.ID = ID;
            this.name = name;
            this.weight = weight;
            this.xPos = xPos;
            this.yPos = yPos;
        }
    }

    /// <summary>
    /// Allows weighted random selection of any object adhering to the IWeightedObject interface.
    /// </summary>
    public class WeightedRandomization
    {
        public static IWeightedObject Choose(List<IWeightedObject> list, Random rand)
        {
            int totalweight = list.Sum(c => c.weight);
            int choice = rand.Next(totalweight);
            int sum = 0;

            foreach (IWeightedObject obj in list)
            {
                for (int i = sum; i < obj.weight + sum; i++)
                {
                    if (i >= choice)
                    {
                        return obj;
                    }
                }
                sum += obj.weight;
            }

            return null;
        }
    }

    #endregion

    #region interfaces

    public interface IWeightedObject
    {
        int weight { get; set; }
    }

    #endregion
}